/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "Renderer.h"
#include "Window.h"
#include "Camera.h"
#include "Light.h"
#include "core/core.h"
#include "Config.h"
//#include "ResourceFactory.h"
#include "InputHandler.h"
#include "gui/DXGUIRenderer.h"
#include "DXDrawPrimitive.h"
#include "IDrawPrimitive.h"

#include <windows.h>
#include <mmsystem.h>
#include <iostream>
#include <sstream>

namespace directx {
                                                               // # of texcoords 
#define D3DFVF_SCREENVERTEX ( D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1 )
struct ScreenVertex
{
	float x, y, z, rhw;
	DWORD color;
	float tu, tv;
};

#define D3DFVF_RECTVERTEX ( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1 )
struct RectVertex
{
	float x, y, z;
	float nx, ny, nz;
	DWORD color;
	float tu, tv;
};

#define D3DFVF_SIMPLEVERTEX ( D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1 )
struct PointVertex
{
	float x, y, z;
	DWORD color;
	float tu, tv;
};

Renderer::Renderer(void)
: bDeviceLost(false),
	multisampleType(D3DMULTISAMPLE_NONE),
	pD3D(NULL),
	pd3dDevice(NULL),
	pBackBuffer(NULL),
	pDepthStencilSurface(NULL),
	dxMatrixStack(NULL),
	pMousePointer(NULL), 
	pSprite(NULL),
	bPrevInitialized(false),
	pLineBuffer(NULL),
	pRectBuffer(NULL),
	pRectScreenBuffer(NULL),
	pStateBlock(NULL),
	m_bBegin(false)
{
}

Renderer::~Renderer(void)
{
	delete dynamic_cast<directx::Camera*>(pCamera);
	pCamera = NULL;

	coreGetLog()->WriteLog("Destroying Rendering context", __FILE__, __LINE__);
	release();

	gui::coreDestroyDXGUIRenderer();

	// Release the d3d Device
	RELEASE( pd3dDevice );
	// Release the directx9 pointer
    RELEASE( pD3D );
}

void Renderer::release()
 {
	if (coreGetScene() && coreGetScene()->isInitialized()) {
		// Release special rendering resources for the IHDRScene
		coreGetScene()->release();
	}

	popState();
	unbindRenderTarget();

	coreGetLog()->WriteLog("Releasing rendering resources", __FILE__, __LINE__);

	RELEASE( dxMatrixStack );
	RELEASE( pLineBuffer );
	RELEASE( pRectBuffer );
	RELEASE( pRectScreenBuffer );
	RELEASE( pSprite );
	RELEASE( pDepthStencilSurface );
	RELEASE( pBackBuffer );
	RELEASE( pStateBlock );
//	RELEASE( pLastDepthStencilSurface );
//	RELEASE( pLastRenderTarget );
}

bool Renderer::readAvailableDisplayModes()
{
	if (!pD3D) 
		return false;

	D3DDISPLAYMODE d3ddm;

	vAvailableDisplayModes.clear();

	std::ostringstream os;
	// Get the 32 bit display modes
	int nMaxAdapterModes = pD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8 );
	for( int nMode=0; nMode<nMaxAdapterModes; ++nMode )
	{
		if( FAILED( pD3D->EnumAdapterModes( D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8, nMode, &d3ddm ) ) )
		{
			coreGetLog()->WriteError("Could not enumerate video adapter modes", __FILE__, __LINE__);
			return false;
		}

		sDisplayMode mode;
		mode.width = d3ddm.Width;
		mode.height = d3ddm.Height;
		mode.refreshRate = d3ddm.RefreshRate;
		mode.bpp = 32;
		
		os << mode.width << "x" << mode.height;
		mode.identifier = os.str();

		vAvailableDisplayModes[mode.identifier] = mode;
		os.str("");
		os.clear();
	}
/*
	// Get the 16 bit display modes
	nMaxAdapterModes = pD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT, D3DFMT_R5G6B5 );
	std::ostringstream os;
	for( int nMode=0; nMode<nMaxAdapterModes; ++nMode )
	{
		if( FAILED( pD3D->EnumAdapterModes( D3DADAPTER_DEFAULT, D3DFMT_R5G6B5, nMode, &d3ddm ) ) )
		{
			coreGetLog()->WriteError("Could not enumerate video adapter modes", __FILE__, __LINE__);
			return false;
		}

		sDisplayMode mode;
		mode.width = d3ddm.Width;
		mode.height = d3ddm.Height;
		mode.refreshRate = d3ddm.RefreshRate;
		mode.bpp = 16;
		
		os << mode.width << "x" << mode.height;
		mode.identifier = os.str();
		vAvailableDisplayModes[mode.identifier] = mode;
		os.str("");
		os.clear();
	}
*/
	return true;
}

bool Renderer::init( Window* window )
{
	pWindow = window;
	DWORD flags = 0;
	bool bDesiredAdapterModeFound = true;
	D3DFORMAT format = D3DFMT_X8R8G8B8;

	// Disable rendering during initialization
	coreSetRendering( false );

	if ( pd3dDevice ) {
		release(); 
		RELEASE( pd3dDevice );
		RELEASE( pD3D );
	}

	// Create the D3D object.
	if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) 
	{
		coreGetLog()->WriteError("Could not create Direct3D. Is it installed?", __FILE__, __LINE__);
		return false;
	}

	readAvailableDisplayModes();
	//
	// For the default adapter, examine all of its display modes to see if any 
	// of them can give us the hardware support we desire.
	//

	if (window->getColorDepth() == 16) 
		format = D3DFMT_R5G6B5;
	
	for (displaymode_list::iterator it = vAvailableDisplayModes.begin(); it != vAvailableDisplayModes.end(); ++it) {
		sDisplayMode dm = it->second;
		if ( dm.width == window->getWidth() 
			&& dm.height == window->getHeight()
			&& dm.bpp == window->getColorDepth()) 
		{
			currentDisplayMode = dm;
			bDesiredAdapterModeFound = true;
		}
	}

	if( bDesiredAdapterModeFound == false )
	{
		coreGetLog()->WriteError("Could not find desired video adapter mode. Changing to default.", __FILE__, __LINE__);
		return false;
	}

	//
	// Here's the manual way of verifying hardware support.
	//

	// Can we get a 32-bit back buffer?
	if( FAILED( pD3D->CheckDeviceType( D3DADAPTER_DEFAULT,
										 D3DDEVTYPE_HAL,
										 format,
										 format,
										 !window->isFullscreen() ) ) )
	{
		coreGetLog()->WriteError("Device type not available", __FILE__, __LINE__);
		return false;
	}


	depthBufferFormat = getBestDepthStencilFormat(format);

	// There was no supported format found
	if (depthBufferFormat == D3DFMT_UNKNOWN) {
		return false;
	}

	//
	// Do we support hardware vertex processing? if so, use it. 
	// If not, downgrade to software.
	//


	if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT,
									   D3DDEVTYPE_HAL, &d3dCaps ) ) )
	{
		coreGetLog()->WriteError("Hardware rendering not available", __FILE__, __LINE__);
		return false;
	}

	if( d3dCaps.VertexProcessingCaps != 0 ) {
		flags = D3DCREATE_HARDWARE_VERTEXPROCESSING;
	}
	else {
		coreGetLog()->WriteWarning("Hardware vertex processing not available. Switching to software mode. This may be extremly slow!", __FILE__, __LINE__);
		flags = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
	}

	//
	// Everything checks out - create a simple, full-screen device.
	//

	memset(&d3dpp, 0, sizeof(d3dpp));
	
	if (window->isFullscreen()) {
		d3dpp.Windowed               = FALSE;
		//d3dpp.FullScreen_RefreshRateInHz = 75;
		d3dpp.hDeviceWindow = window->getHWnd();
		d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
	}
	else {
		d3dpp.Windowed               = TRUE;
		d3dpp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
	}

	d3dpp.EnableAutoDepthStencil = TRUE;
	d3dpp.AutoDepthStencilFormat = depthBufferFormat;
	d3dpp.BackBufferWidth        = window->getWidth();
	d3dpp.BackBufferHeight       = window->getHeight();
    d3dpp.BackBufferFormat       = format;

	int backbuffercount = 2;
	/*
	if (coreGetConfig()->isDefined("r_backbuffercount")) 
		backbuffercount = coreGetConfig()->getNumber<int>("r_backbuffercount");
	*/
	// TODO Set tripple buffering
	d3dpp.BackBufferCount       = backbuffercount;

	// If vertical sync is enabled
	if (window->hasVSync()) 
		d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT;
	else
		d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

	if( FAILED( hr = pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, window->getHWnd(),
										  flags, &d3dpp, &pd3dDevice ) ) )
	{
		coreGetLog()->WriteError("Could not create DirectX Device", __FILE__, __LINE__);
		if (hr == D3DERR_DEVICELOST)
			coreGetLog()->WriteError("Reason: Device lost.", __FILE__, __LINE__);
		else if (hr == D3DERR_INVALIDCALL)
			coreGetLog()->WriteError("Reason: invalid call.", __FILE__, __LINE__);
		else if (hr == D3DERR_NOTAVAILABLE)
			coreGetLog()->WriteError("Reason: Device not available.", __FILE__, __LINE__);
		else if (hr == D3DERR_OUTOFVIDEOMEMORY) 
			coreGetLog()->WriteError("Reason: Out of video memory.", __FILE__, __LINE__);
		return false;
	}

	/// TODO Initialize the depth stencil buffer!!

	if (bPrevInitialized) 
		resetDevice();

	create();
		
	if (!pCamera)
		pCamera = new directx::Camera();

	gui::coreInitDXGUIRenderer(pd3dDevice);

	bPrevInitialized = true;

	// Enable backface culling
	//pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);

	// Disable rendering during initialization
	coreSetRendering( true );
	return true;
}

D3DFORMAT Renderer::getBestDepthStencilFormat(D3DFORMAT backbufferFormat, D3DFORMAT desiredFormat)
{
	// Can we get a z-buffer that's at least 16 bits?
	if( SUCCEEDED( pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,
                                           D3DDEVTYPE_HAL,
										   backbufferFormat,
                                           D3DUSAGE_DEPTHSTENCIL,
                                           D3DRTYPE_SURFACE,
										   desiredFormat ) ) )
    {
		coreGetLog()->WriteLog("Desired depth stencil surface supported.", __FILE__, __LINE__);
		return desiredFormat;
	}
	
	// Can we get a z-buffer that's at least 16 bits?
	if( SUCCEEDED( pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,
                                           D3DDEVTYPE_HAL,
										   backbufferFormat,
                                           D3DUSAGE_DEPTHSTENCIL,
                                           D3DRTYPE_SURFACE,
										   D3DFMT_D24S8 ) ) )
    {
		coreGetLog()->WriteLog("Depth stencil surface of D32S8 found", __FILE__, __LINE__);
		return D3DFMT_D24S8;
	}

	// Can we get a z-buffer that's at least 16 bits?
	if( SUCCEEDED( pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,
                                           D3DDEVTYPE_HAL,
										   backbufferFormat,
                                           D3DUSAGE_DEPTHSTENCIL,
                                           D3DRTYPE_SURFACE,
                                           D3DFMT_D16 ) ) )
    {
		coreGetLog()->WriteLog("Depth stencil surface of D16 found", __FILE__, __LINE__);
		return D3DFMT_D16;
	}
	else {
		coreGetLog()->WriteLog("Could not find the minimum depth stencil surface format", __FILE__, __LINE__);
		return D3DFMT_UNKNOWN;
	}
}

bool Renderer::IsTextureFormatOk( D3DFORMAT TextureFormat, D3DFORMAT AdapterFormat, DWORD Usage) 
{
    HRESULT hr = pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,
                                          D3DDEVTYPE_HAL,
                                          AdapterFormat,
                                          Usage,
                                          D3DRTYPE_TEXTURE,
                                          TextureFormat);
    
    return SUCCEEDED( hr );
}

D3DFORMAT Renderer::getAdapterFormat()
{
	return d3dpp.BackBufferFormat;
}

bool Renderer::isGammaSupported()
{
	return (d3dCaps.Caps2 & D3DCAPS2_FULLSCREENGAMMA) ? true : false;
}

void Renderer::create()
{
	// Device state would normally be set here
	// Turn on the zbuffer
    pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );

    // Turn on ambient lighting 
    pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x00000000 );

    // Turn off culling
    pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

	// Get the default render target and create a depth stencil surface. 
	// This is important when using multiple rendering targets
	pd3dDevice->GetRenderTarget(0, &pBackBuffer);
	pBackBuffer->GetDesc( &backBufferDesc );
	m_vBackBuffer = math::Vector2i(static_cast<int>(backBufferDesc.Width), static_cast<int>(backBufferDesc.Height));
	pd3dDevice->CreateDepthStencilSurface( backBufferDesc.Width, 
											backBufferDesc.Height, 
											depthBufferFormat,
											multisampleType, 0, FALSE,
											&pDepthStencilSurface,
											NULL);

	// Create a matrix stack for pushMatrix() and popMatrix()
	if (FAILED( D3DXCreateMatrixStack(0, &dxMatrixStack) )) 
	{
		coreGetLog()->WriteError("Could not create DirectX Matrix stack", __FILE__, __LINE__);
	} 
	else {
		// Load the identity of the matrix stack
		dxMatrixStack->LoadIdentity();
	}
}


void Renderer::resetDevice()
{
	// Try to reset the device
	if (FAILED( pd3dDevice->Reset( &d3dpp ) ))
	{
		coreGetLog()->WriteLog("Resetting DirectX device failed", __FILE__, __LINE__);
	}

	create();
	
	// Reset everything created by an ResourceFactory
	coreGetResourceFactory()->resetAll();

	// Reset the projection
	setupProjection( fFov, fZNear, fZFar );

	// FIXME Why here?
	if ( coreGetInputHandler() ) {
		coreGetInputHandler()->restore();
	}

	// FIXME Why here?
	if (coreGetScene() && !coreGetScene()->isInitialized()) {
		// Recreate special rendering resources for the IHDRScene
		coreGetScene()->restore();
	}

}

D3DMULTISAMPLE_TYPE Renderer::getMultisampleType() const
{
	return multisampleType;
}

void Renderer::clearScene(const math::Vector4 &clearColor) 
{
	pd3dDevice->Clear(0, 
					NULL, 
					D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL, 
					D3DCOLOR_COLORVALUE(clearColor.x, clearColor.y, clearColor.z, clearColor.w), 
					1.0f, 
					0);
	DXLight::iLightCounter = 0;
	pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
}

void Renderer::drawCamera(ICamera *cam) 
{
	static ICamera *current;

	// Choose the camera. If cam is not provided, use the internal one.
	if (cam != NULL) 
		current = cam;
	else
		current = pCamera;

	// Draw the camera (setup the matrices)
	current->draw();

	// Set our view matrix
	D3DXMATRIX dxViewMat = dynamic_cast<directx::Camera*>(current)->getViewMatrix();
	D3DXMATRIX dxProjMat = dynamic_cast<directx::Camera*>(current)->getProjMatrix();

	projMat = math::Matrix4( &(dxProjMat._11) );
	viewMat = math::Matrix4( &(dxViewMat._11) );

	pCurrentCam = current;

	pCurrentCam->calculateFrustum();
}

void Renderer::resize(int width, int height)
{
	// TODO Implement
}

void Renderer::setMousePointer(ITexture* texture, const math::Vector4& color)
{
	pMousePointer = (directx::Texture*)texture;
	m_vMouseColor = color;
}

void Renderer::drawMouse(int x, int y, bool center)
{
	drawSprite(pMousePointer, math::Vector3(static_cast<float>(x), static_cast<float>(y), 0), center, m_vMouseColor);
}

void Renderer::translate(const math::Vector3 &vec)
{
	D3DXMATRIX mat;
	D3DXMatrixTranslation(&mat, vec.x, vec.y, vec.z);
	applyMatrix( mat );
}

void Renderer::rotate(float x, float y, float z)
{
	D3DXMATRIX mat;
	D3DXMatrixRotationYawPitchRoll(&mat, D3DXToRadian(x), D3DXToRadian(y), D3DXToRadian(z));
	applyMatrix( mat );
}

/*
void Renderer::rotate(math::Quaternion q)
{ // irgendwie geht das nicht...
	D3DXMATRIX mat;
	D3DXQUATERNION quat;
	quat.w = q.w;
	quat.x = q.x;
	quat.y = q.y;
	quat.z = q.z;
	D3DXMatrixRotationQuaternion( &mat, &quat);
	applyMatrix( mat );
}
*/

void Renderer::scale(float x, float y, float z)
{
	D3DXMATRIX mat;
	D3DXMatrixScaling(&mat, x, y, z);
	applyMatrix( mat );
}

void Renderer::loadIdentity()
{
	dxMatrixStack->LoadIdentity();
	D3DXMATRIX *mat = getMatrix();
	pd3dDevice->SetTransform( D3DTS_WORLD, mat );
	worldMat = math::Matrix4( &(mat->_11) );
}

void Renderer::pushMatrix()
{
	dxMatrixStack->Push();
}

void Renderer::popMatrix()
{
	dxMatrixStack->Pop();
	D3DXMATRIX *mat = getMatrix();
	pd3dDevice->SetTransform( D3DTS_WORLD, mat );
	worldMat = math::Matrix4( &(mat->_11) );
}

void Renderer::applyMatrix(const math::Matrix4 &matrix)
{
	applyMatrix( (D3DXMATRIX) matrix );
}

void Renderer::applyMatrix( D3DXMATRIX matrix )
{
	dxMatrixStack->MultMatrixLocal(&matrix); 
	D3DXMATRIX *mat = getMatrix();
	pd3dDevice->SetTransform( D3DTS_WORLD, mat );
	worldMat = math::Matrix4( &(mat->_11) );
}

D3DXMATRIX* Renderer::getMatrix()
{
	return dxMatrixStack->GetTop();
}

void Renderer::setupProjection(float fov, float znear, float zfar)
{
	D3DXMATRIX dxProjMat;

	fFov = fov; 
	fZNear = znear; 
	fZFar = zfar;

	float aspect = coreGetMainWindow()->getWidth() / (float)coreGetMainWindow()->getHeight();
	
	pCamera->setupProjection(fFov, aspect, fZNear, fZFar);
}

bool Renderer::beginScene()
{
	if (!m_bBegin) {
		m_bBegin = true;
		if (SUCCEEDED( pd3dDevice->BeginScene() ))
			return true;
		else
			return false;
	}
	return false;
}

void Renderer::endScene()
{
	if (m_bBegin) {
		pd3dDevice->EndScene();
		m_bBegin = false;
	}
}

bool Renderer::prepareBuffer()
{
	// Has the device been lost in the previous frame?
	if (bDeviceLost) 
	{
		// Wait for 100 ms
		Sleep(100);

		// Test the cooperative level to see if the device can be reset.
		if( FAILED( hr = pd3dDevice->TestCooperativeLevel() ) )
        {
			// Still lost: return
			if( hr == D3DERR_DEVICELOST ) {
				return false;
			}

			// The device has been lost but it can be reset at this time. 
			if( hr == D3DERR_DEVICENOTRESET )
			{
				coreGetLog()->WriteLog("Resetting DirectX device", __FILE__, __LINE__);
				release();
				resetDevice();
			}

			return false;
		}
		
		// temp
		release();
		resetDevice();

		bDeviceLost = false;
	}
	return true;
}

void Renderer::swapBuffer()
{
	// Present the backbuffer contents to the display
 	if (FAILED( hr = pd3dDevice->Present(NULL, NULL, NULL, NULL) ))
	{
		// If Present failed, then the device may be lost
		if (hr == D3DERR_DEVICELOST) 
			bDeviceLost = true;
		else if (hr == D3DERR_DRIVERINTERNALERROR) 
			coreGetLog()->WriteError("SwapBuffer failed: Internal Driver Error", __FILE__, __LINE__);
	}
}

const math::Matrix4& Renderer::getViewMatrix() const
{
	return viewMat;
}

const math::Matrix4& Renderer::getProjMatrix() const
{
	return projMat;
}

const math::Matrix4& Renderer::getWorldMatrix() const
{
	return worldMat;
}

math::Matrix4 Renderer::getViewProjMatrix() const
{
	return (viewMat * projMat);
}

math::Matrix4 Renderer::getWorldProjMatrix() const
{
	return (worldMat * projMat);
}

math::Matrix4 Renderer::getWorldViewMatrix() const
{
	return (worldMat * viewMat);
}

math::Matrix4 Renderer::getWorldViewProjMatrix() const
{
	return (worldMat * viewMat * projMat);
}

bool Renderer::bindRenderTarget(ITexture *texture, int dest)
{
	// Cast the texture to the directx specific device
	directx::Texture *tex = (directx::Texture*)texture;

	// The the render target to the texture
	if (FAILED( pd3dDevice->SetRenderTarget(dest, tex->getRenderTarget() ) ))
	{
		coreGetLog()->WriteError("Could not bind render target", __FILE__, __LINE__);
		return false;
	}
	// If the dest is the main texture, bind the depth and stencil surface
	if (dest == 0 && texture->hasDepthBuffer())
	{
		// Bind the depth and stencil surface to the on in the texture
		if (FAILED( pd3dDevice->SetDepthStencilSurface( tex->getRenderTargetDepthStencil() ) ))
		{
			coreGetLog()->WriteError("Could not set depth and stencil buffer for render target", __FILE__, __LINE__);
			return false;
		}
	}
	return true;
}

bool Renderer::unbindRenderTarget(int dest)
{
	if (dest != 0) {		
		// Set the default render target, i.e. the back buffer
		if (FAILED( pd3dDevice->SetRenderTarget(dest, NULL) ))
		{
			coreGetLog()->WriteError("Could not unbind render target", __FILE__, __LINE__);
			return false;
		}		
	} else {
		// Set the default render target, i.e. the back buffer
		if (FAILED( pd3dDevice->SetRenderTarget(0, pBackBuffer) ))
		{
			coreGetLog()->WriteError("Could not unbind render target", __FILE__, __LINE__);
			return false;
		}
		// Set the depth and stencil surface associated with the back buffer
		if (FAILED( pd3dDevice->SetDepthStencilSurface( pDepthStencilSurface ) ))
		{
			coreGetLog()->WriteError("Could not set depth and stencil buffer for back buffer", __FILE__, __LINE__);
			return false;
		}
	}

	return true;
}

void Renderer::drawScreenQuad(int width, int height, int offsetX, int offsetY, float scaleFactor, float texScale)
{
    LPDIRECT3DVERTEXBUFFER9	pFullBuffer = NULL;
	void *pVertices = NULL;

	if (width == -1) 
		width = getBackBufferSize().x;
	if (height == -1) 
		height = getBackBufferSize().y;

	ScreenVertex fullVertices[] = 
	{
		{(float)offsetX-.5f,						(float)offsetY-.5f,							0.0f,  1.0f, D3DCOLOR_COLORVALUE(1, 1, 1, 1), 0.0f,            0.0f },
		{(float)(offsetX+width*scaleFactor)-.5f,	(float)offsetY-.5f,							0.0f,  1.0f, D3DCOLOR_COLORVALUE(1, 1, 1, 1), texScale*1.0f,   0.0f },
		{(float)offsetX-.5f,						(float)(offsetY+height*scaleFactor)-.5f,	0.0f,  1.0f, D3DCOLOR_COLORVALUE(1, 1, 1, 1), 0.0f,            texScale*1.0f },
		{(float)(offsetX+width*scaleFactor)-.5f,	(float)(offsetY+height*scaleFactor)-.5f,	0.0f,  1.0f, D3DCOLOR_COLORVALUE(1, 1, 1, 1), texScale*1.0f,   texScale*1.0f },
	};

	if (SUCCEEDED( pd3dDevice->CreateVertexBuffer( 4 * sizeof( ScreenVertex ),
									D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, 
									D3DFVF_SCREENVERTEX,
                                    D3DPOOL_DEFAULT, 
									&pFullBuffer, NULL ) )) 
	{

		pFullBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
		memcpy( pVertices, fullVertices, sizeof(fullVertices) );
		pFullBuffer->Unlock();

		// Render the quad on screen
		pd3dDevice->SetStreamSource( 0, pFullBuffer, 0, sizeof( ScreenVertex ) );
		pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
		pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );

		RELEASE( pFullBuffer );
	}
}

void Renderer::setBackBufferSize(const math::Vector2i &size)
{
	m_vBackBuffer = size;
}

void Renderer::resetBackBufferSize()
{
	m_vBackBuffer = math::Vector2i(static_cast<int>(backBufferDesc.Width), static_cast<int>(backBufferDesc.Height));
}
const math::Vector2i& Renderer::getBackBufferSize() const
{
	return m_vBackBuffer;
}

void Renderer::pushState()
{
	RELEASE(pStateBlock);
	pd3dDevice->CreateStateBlock( D3DSBT_ALL, &pStateBlock );
}

void Renderer::popState()
{
	if (pStateBlock) 
		pStateBlock->Apply();

	RELEASE( pStateBlock );
}

void Renderer::drawRect(int x, int y, int width, int height, const math::Vector4 &color, bool fill)
{
	//LPDIRECT3DVERTEXBUFFER9	pFullBuffer = NULL;
	void *pVertices = NULL;

	if (fill) {
		ScreenVertex fullVertices[] = 
		{
				//	X		Y		Z		RHW		U		V
				{static_cast<float>(x),			static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   0.0f },
				{static_cast<float>(x+width),	static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
				{static_cast<float>(x),			static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   1.0f },
				{static_cast<float>(x+width),	static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   1.0f },
		};

		if (!pRectScreenBuffer) {
			pd3dDevice->CreateVertexBuffer( 5 * sizeof( ScreenVertex ),
											D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFVF_SCREENVERTEX,
											D3DPOOL_DEFAULT, &pRectScreenBuffer, NULL );
		}

		pRectScreenBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
		memcpy( pVertices, fullVertices, sizeof(fullVertices) );
		pRectScreenBuffer->Unlock();

		// Render the quad on screen
		pd3dDevice->SetStreamSource( 0, pRectScreenBuffer, 0, sizeof( ScreenVertex ) );
		pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
		pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,  0, 2 );
	} else {
		ScreenVertex fullVertices[] = 
		{
		//	X		Y		Z			U		V
				{static_cast<float>(x),			static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   0.0f,   0.0f },
				{static_cast<float>(x+width),	static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
				{static_cast<float>(x+width),	static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
				{static_cast<float>(x),			static_cast<float>(y+height),	0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
				{static_cast<float>(x),			static_cast<float>(y),			0.0f,	1.0f,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w),   1.0f,   0.0f },
		};

		if (!pRectScreenBuffer) {
			pd3dDevice->CreateVertexBuffer( 5 * sizeof( ScreenVertex ),
											D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFVF_SCREENVERTEX,
											D3DPOOL_DEFAULT, &pRectScreenBuffer, NULL );
		}

		pRectScreenBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
		memcpy( pVertices, fullVertices, sizeof(fullVertices) );
		pRectScreenBuffer->Unlock();

		// Render the quad on screen
		pd3dDevice->SetStreamSource( 0, pRectScreenBuffer, 0, sizeof( ScreenVertex ) );
		pd3dDevice->SetFVF( D3DFVF_SCREENVERTEX );
		pd3dDevice->DrawPrimitive( D3DPT_LINESTRIP,  0, 4 );
	}

	//RELEASE( pFullBuffer );
}

void Renderer::drawSprite(ITexture* texture, const math::Vector3 &pos, bool transform, const math::Vector4& color)
{
	pushMatrix();
	if (!pSprite) 
		D3DXCreateSprite(pd3dDevice, &pSprite);

	DWORD flags = D3DXSPRITE_ALPHABLEND;
	float posX = 0;
	float posY = 0;
	if (transform) {
		posX = texture->getWidth()/2.0f;
		posY = texture->getHeight()/2.0f;
		//flags |= D3DXSPRITE_OBJECTSPACE;
	}
	pSprite->Begin(flags);

	pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);

	// Enable alpha blending
	pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	// Set the source blend state.
	pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	// Set the destination blend state.
	pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

	if (FAILED( pSprite->Draw(dynamic_cast<directx::Texture*>(texture)->getD3DTexture(), NULL, &D3DXVECTOR3(posX, posY, 0), &D3DXVECTOR3(pos.x, pos.y, pos.z), D3DCOLOR_COLORVALUE(color.x,color.y,color.z,color.w)) ))
	{
		coreGetLog()->WriteWarning("Failed to draw sprite", __FILE__, __LINE__);
	}
	pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, TRUE);
	pSprite->End();
	popMatrix();
}

void Renderer::drawSphere(float radius, const math::Vector4 &color)
{
	IDrawPrimitive *mesh = NULL;
	mesh = IDrawPrimitive::CreateSphere(radius, 16, 16);
	drawPrimitive(mesh);
	//pd3dDevice->SetMaterial( NULL );
	DELETE_POINTER(mesh);
}

void Renderer::drawBox(float width, float height, float depth, const math::Vector4 &color)
{
	LPD3DXMESH mesh;
	D3DXCreateBox( pd3dDevice, width, height, depth, &mesh, NULL);
	mesh->DrawSubset(0);
	RELEASE( mesh );
}

void Renderer::drawLine(const math::Vector3 &from, const math::Vector3 &to, const math::Vector4 &color)
{
	static void* pVertices = NULL;
	pushState();
	pd3dDevice->SetRenderState(D3DRS_LIGHTING, false);

	PointVertex fullVertices[] = 
	{
		//	X		Y		Z		COLOR													 U	   V
		{from.x,	from.y,	from.z,	D3DCOLOR_COLORVALUE(color.x, color.y, color.z, color.w), 0.0f, 0.0f },
		{to.x,		to.y,	to.z,	D3DCOLOR_COLORVALUE(1.0, 1.0, 1.0, 1.0), 0.0f, 0.0f },
	};

	//if (!pLineBuffer)
	pd3dDevice->CreateVertexBuffer( 2 * sizeof( PointVertex ), D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 
										D3DFVF_SIMPLEVERTEX,
										D3DPOOL_DEFAULT, 
										&pLineBuffer, NULL );

	pLineBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
	memcpy( pVertices, fullVertices, sizeof(fullVertices) );
	pLineBuffer->Unlock();

	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, pLineBuffer, 0, sizeof( PointVertex ) );
	pd3dDevice->SetFVF( D3DFVF_SIMPLEVERTEX );
	pd3dDevice->DrawPrimitive( D3DPT_LINELIST,  0, 1 );

	RELEASE(pLineBuffer);
	popState();
}

void Renderer::drawPlane(const math::Vector3 &normal, const float d, const math::Vector2f &dimension)
{
	static void* pVertices = NULL;

	math::Vector3 u = normal.crossProduct(math::Vector3(0,0,1));
	math::Vector3 v = normal.crossProduct(u);

	math::Vector3 p0 = normal * d;

	u *= dimension.x;
	v *= dimension.y;

	math::Vector3 p1 = p0 - u - v;
	math::Vector3 p2 = p0 + u - v;
	math::Vector3 p3 = p0 + u + v;
	math::Vector3 p4 = p0 - u + v;

	RectVertex fullVertices[] = {
			//	X	Y	Z		COLOR							U		V
			{p1.x, p1.y, p1.z,	normal.x, normal.y, normal.z, D3DCOLOR_COLORVALUE(1,1,1,1),   0.0f,	0.0f },
			{p2.x, p2.y, p2.z,	normal.x, normal.y, normal.z, D3DCOLOR_COLORVALUE(1,1,1,1),   1.0f,	0.0f },
			{p3.x, p3.y, p3.z,	normal.x, normal.y, normal.z, D3DCOLOR_COLORVALUE(1,1,1,1),   1.0f,	1.0f },
			{p4.x, p4.y, p4.z,	normal.x, normal.y, normal.z, D3DCOLOR_COLORVALUE(1,1,1,1),   0.0f,	1.0f },
	};

	if (!pRectBuffer) {
		pd3dDevice->CreateVertexBuffer( 4 * sizeof( RectVertex ),
										D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, D3DFVF_RECTVERTEX,
										D3DPOOL_DEFAULT, &pRectBuffer, NULL );
	}

	pRectBuffer->Lock( 0, sizeof(fullVertices), (void**)&pVertices, D3DLOCK_DISCARD );
	memcpy( pVertices, fullVertices, sizeof(fullVertices) );
	pRectBuffer->Unlock();

	// Render the quad on screen
	pd3dDevice->SetStreamSource( 0, pRectBuffer, 0, sizeof( RectVertex ) );
	pd3dDevice->SetFVF( D3DFVF_RECTVERTEX );
	pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN,  0, 2 );
}

inline int
ramp_val(UINT i, float recip_gamma, int scale = 65535)
{
    return static_cast<int>(scale*pow(i/255.f, recip_gamma));
}

//-----------------------------------------------------------------------------
// recip_gamma
//
// Given a gamma corrected i in [0,255], return 1/gamma
//
inline float
recip_gamma(UINT i)
{
    return logf(i/255.f)/logf(0.5f);
}

void Renderer::setGammaRamp(float gammaFactor)
{
	D3DGAMMARAMP gammaRamp;

	if (!(d3dCaps.Caps2 & D3DCAPS2_CANCALIBRATEGAMMA )) {
		coreGetLog()->WriteWarning("Device does not support gamma ramp", __FILE__, __LINE__);
	}

	//coreGetLog()->WriteLog("Setting gamma ramp to reference value " << gammaFactor, __FILE__, __LINE__);

	pd3dDevice->GetGammaRamp(0, &gammaRamp);

	for (int i=0; i<256; ++i) {
		gammaRamp.red[i] = 
		gammaRamp.green[i] = 
		gammaRamp.blue[i] = ramp_val(i, recip_gamma((UINT)gammaFactor));
	}

	pd3dDevice->SetGammaRamp(0, D3DSGR_NO_CALIBRATION, &gammaRamp);
}

void Renderer::setWireframe(bool value)
{
	if (value) 
		pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
	else
		pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
}

bool Renderer::supportsR2VB()
{
	HRESULT hr = pD3D->CheckDeviceFormat(	D3DADAPTER_DEFAULT, 
											D3DDEVTYPE_HAL, 
											getAdapterFormat(), 
											0, 
											D3DRTYPE_SURFACE, 
											(D3DFORMAT) MAKEFOURCC('R', '2', 'V', 'B') );
	if (SUCCEEDED( hr )) {
		coreGetLog()->WriteLog("Renderer supports R2VB", __FILE__, __LINE__);
		return true;
	}
	return false;
}

bool Renderer::supportsFP16Filter() 
{
	HRESULT hr = pD3D->CheckDeviceFormat(	D3DADAPTER_DEFAULT, 
											D3DDEVTYPE_HAL, 
											getAdapterFormat(), 
											D3DUSAGE_QUERY_FILTER, 
											D3DRTYPE_TEXTURE, 
											D3DFMT_A16B16G16R16F);
	if (SUCCEEDED( hr) ) {
		return true;
	}
	return false;
}

bool Renderer::supportsMRT(int numRT)
{
	return (d3dCaps.NumSimultaneousRTs >= (DWORD)numRT);
}

bool Renderer::hasMinimumShaderLevel(int majVS, int minVS, int majPS, int minPS)
{
	if (d3dCaps.VertexShaderVersion < D3DVS_VERSION(majVS, minVS) )
		return false;

	if (d3dCaps.PixelShaderVersion < D3DVS_VERSION(majPS, minPS) )
		return false;

	return true;
}

void Renderer::drawPrimitive(IDrawPrimitive *primitive)
{
	pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
	//dynamic_cast<DXDrawPrimitive*>(primitive)->m_pMesh->DrawSubset(0);
	primitive->draw();
	//pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
}

void Renderer::setClipPlane(IDrawPrimitive *primitive)
{
	static D3DXMATRIX directInvMat;
	static math::Matrix4 invView;

	// Disable
	if (primitive == NULL) {
		pd3dDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, 0);
		pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
		return;
	}
	// Enable
	if (primitive->type == TYPE_PLANE) {
		D3DXPLANE transformedPlane;
		D3DXPLANE reflectionClipPlaneAboveWater = D3DXPLANE(primitive->normal.x,  -primitive->normal.y, primitive->normal.z, primitive->distance);
		D3DXPlaneNormalize( &reflectionClipPlaneAboveWater, &reflectionClipPlaneAboveWater );

		// Laut Luna muss man den 4er-Vektor der Plane (Normale, Abstand) mit der invers-transponierten Matrix derjenigen multiplizieren,
		// die man auf die Plane anwenden will. Will man also in mit der ViewMatrix multiplizieren, muss diese vorher invers-transponiert werden.
		pd3dDevice->SetRenderState(D3DRS_CLIPPLANEENABLE, 1);
		pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
		
		invView = getViewProjMatrix();		
		D3DXMatrixInverse(&directInvMat, 0, (D3DXMATRIX*)&invView[0]);
		D3DXMatrixTranspose( &directInvMat, &directInvMat );

		D3DXPlaneTransform( &transformedPlane, &reflectionClipPlaneAboveWater, (D3DXMATRIX*)&directInvMat);

		pd3dDevice->SetClipPlane( 0, (float*)transformedPlane);
	}
}

const math::Matrix4& Renderer::createReflectionMatrix(IDrawPrimitive *primitive)
{
	static D3DXMATRIX RefMtx;
	
	if (primitive->type == TYPE_PLANE) {
		D3DXMatrixIdentity(&RefMtx);
		D3DXPLANE reflectionClipPlaneAboveWater = D3DXPLANE(primitive->normal.x, -primitive->normal.y, primitive->normal.z, primitive->distance);
		D3DXMatrixReflect(&RefMtx, &reflectionClipPlaneAboveWater);
		m_mReflectionMat = math::Matrix4(&RefMtx[0]);
	} else {
		m_mReflectionMat.setIdentity();
	}
	
	return m_mReflectionMat;
}

void Renderer::mouseToWorld(int mx, int my, math::Vector3* outStart, math::Vector3* outDir)
{
	static D3DXVECTOR3 originW, dirW;
	static math::Vector2i screenSize = getBackBufferSize();
	static math::Matrix4 proj;
	proj = getProjMatrix();
	float _x = (2.0f*mx/screenSize.x - 1.0f) / proj.m[0];
	float _y = (-2.0f*my/screenSize.y +1.0f) / proj.m[5];

	D3DXVECTOR3 orig(0,0,0);
	D3DXVECTOR3 dir(_x,_y,1.0f);
	
	D3DXMATRIX invView;
	D3DXMatrixInverse(&invView, 0, (D3DXMATRIX*)&viewMat[0]);
	D3DXVec3TransformCoord((D3DXVECTOR3*)outStart, &orig, &invView);
	D3DXVec3TransformNormal((D3DXVECTOR3*)outDir, &dir, &invView);
	D3DXVec3Normalize((D3DXVECTOR3*)outDir, (D3DXVECTOR3*)outDir);	
}

bool Renderer::supportsHDR()
{
	// check support for a Format.A16B16R16F render target
	if (!pD3D->CheckDeviceFormat(D3DADAPTER_DEFAULT, 
									D3DDEVTYPE_HAL, 
									getAdapterFormat(),
									D3DUSAGE_RENDERTARGET, 
									D3DRTYPE_CUBETEXTURE,
									D3DFMT_A16B16G16R16F)) {
		return true;
	} else {
		return false;
	}
}

} // namespace
